Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Rendered β€’ View raw β€’ Download

specs/20260521-153452-car-app-library-integration/quickstart.md bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 5.15 KB

Quickstart: Car App Library Integration

Feature: Car App Library Integration
Date: 2026-05-21

Prerequisites

β€’ Android Studio Ladybug or newer (for CAL preview tools)
β€’ JDK 21 (T383838JAVA_HOME set)
β€’ T383838ANDROID_HOME set with API 35+ SDK installed
β€’ Proto submodule initialized: T383838git submodule update --init
β€’ T383838local.properties configured: T383838cp secrets.defaults.properties local.properties
β€’ Android Auto Desktop Head Unit (DHU) installed via SDK Manager β†’ SDK Tools β†’ Android Auto Desktop Head Unit

Setup

1. Sync and Build

T282828
T8b949e# Full sync (includes new :feature:car module)
./gradlew sync

T8b949e# Build google flavor (required β€” car module is google-only)
./gradlew assembleGoogleDebug


2. Install DHU for Testing

The Desktop Head Unit simulates Android Auto on your development machine.

T282828
T8b949e# Install via SDK Manager (or command line)
sdkmanager Ta5d6ff"extras;google;auto"

T8b949e# Start DHU (after connecting a device/emulator with the app installed)
Te6edf3$ANDROID_HOME/extras/google/auto/desktop-head-unit


3. Run on Android Auto (Projection Mode)

1. Install the google debug build on a physical device: T383838./gradlew installGoogleDebug
2. Enable Developer Mode in Android Auto settings on the phone
3. Start the DHU: T383838desktop-head-unit
4. The Meshtastic car app appears in the DHU's app launcher under "Messaging" category

4. Run on AAOS Emulator

T282828
T8b949e# Create AAOS emulator (API 33+ automotive system image)
avdmanager create avd -n Ta5d6ff"AAOS_Test" -k Ta5d6ff"system-images;android-33;google_apis_playstore;x86_64" --device Ta5d6ff"automotive_1024p_landscape"

T8b949e# Start emulator
emulator -avd AAOS_Test

T8b949e# Install
./gradlew installGoogleDebug


Development Workflow

Module Location

All car-specific code lives in T383838feature/car/:

T282828
feature/car/src/main/kotlin/org/meshtastic/feature/car/
β”œβ”€β”€ di/ β†’ Koin DI module
β”œβ”€β”€ service/ β†’ CarAppService + Session
β”œβ”€β”€ screens/ β†’ CAL Screen implementations
β”œβ”€β”€ alerts/ β†’ Emergency banner handler
β”œβ”€β”€ panels/ β†’ Minimized Control Panel
└── util/ β†’ Helpers (Crashlytics tagger, template builders)

Key Development Patterns

Screen implementation:
T282828
Tff7b72class T56d364MessagingScreenTb4b4b4(Te6edf3carContextTb4b4b4: Te6edf3CarContextTb4b4b4) Tb4b4b4: Te6edf3ScreenTb4b4b4(Te6edf3carContextTb4b4b4) Tb4b4b4{
T8b949e// Inject repositories via Koin
Tff7b72private Tff7b72val Te6edf3packetRepositoryTb4b4b4: Te6edf3PacketRepository Tff7b72by Te6edf3injectTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffonGetTemplateTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3Template Tb4b4b4{
T8b949e// Build template from current state
T8b949e// Call invalidate() when data changes to trigger re-render
Tb4b4b4}
Tb4b4b4}


Data observation (CAL doesn't use Compose β€” use coroutine collection):
T282828
T8b949e// In Screen's lifecycle, collect flows and call invalidate()
Te6edf3lifecycleScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3repositoryTb4b4b4.Te6edf3getContactsTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3collect Tb4b4b4{ Te6edf3contacts Tff7b72-Tff7b72>
Te6edf3cachedContacts Tff7b72= Te6edf3contacts
Te6edf3invalidateTb4b4b4(Tb4b4b4) T8b949e// Triggers onGetTemplate() re-call
Tb4b4b4}
Tb4b4b4}


Template refresh: CAL screens are invalidated manually β€” no reactive binding. Call T383838invalidate() whenever backing data changes.

Testing

T282828
T8b949e# Unit tests (uses androidx.car.app:app-testing)
./gradlew :feature:car:testGoogleDebugUnitTest

T8b949e# Lint + formatting
./gradlew :feature:car:spotlessApply :feature:car:spotlessCheck :feature:car:detekt


Test approach: Use T383838SessionController and T383838TestCarContext from T383838app-testing artifact to simulate host interactions without a real car/DHU.

T282828
Tf0883e@Test
Tff7b72fun Td2a8ff`messaging screen shows conversations`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3controller Tff7b72= Te6edf3SessionControllerTb4b4b4(
Te6edf3MeshtasticCarSessionTb4b4b4(Te6edf3testSessionInfoTb4b4b4)Tb4b4b4,
Te6edf3TestCarContextTb4b4b4(Te6edf3ApplicationProviderTb4b4b4.Te6edf3getApplicationContextTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4)
T8b949e// Push screen, assert template content
Tb4b4b4}


Debugging

β€’ CAL Logcat filter: T383838tag:CarApp OR tag:CarService
β€’ Template errors: CAL validates templates at runtime β€” check logcat for T383838TemplateValidationException
β€’ Screen stack: Use T383838ScreenManager.getTop() to inspect current screen
β€’ Crashlytics: Filter by T383838car_session custom key in Firebase Console

Common Tasks

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Task β”‚ Command / Action β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Add a new screen β”‚ Create T383838Screen subclass in T383838screens/, register in navigation β”‚
β”‚ Add a CAL dependency β”‚ Update T383838gradle/libs.versions.toml + T383838feature/car/build.gradle.kts β”‚
β”‚ Test with DHU β”‚ T383838desktop-head-unit after installing google debug build β”‚
β”‚ Check template compliance β”‚ Run app on DHU; host validates template constraints β”‚
β”‚ Filter car crashes β”‚ Firebase Console β†’ Crashlytics β†’ Filter: T383838car_session is not empty β”‚
β”‚ Full verification β”‚ T383838./gradlew spotlessApply :feature:car:spotlessCheck :feature:car:det… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Architecture Notes

β€’ No Compose: CAL uses its own template-based rendering. Don't mix Compose APIs.
β€’ No T383838commonMain: This is an Android-only module. All code in T383838src/main/kotlin/.
β€’ Shared BLE: Don't create new BLE connections. Inject existing T383838BleConnection singleton.
β€’ Koin DI: All core repositories are already in the graph. Just T383838inject() them.
β€’ Flavor: Only T383838google flavor includes this module. Never reference it from T383838fdroid code.

Served by rngit 1.5.4 - Generated in 0.05s